use crate::{
    action::user::{UpdatePasswordError, UpdateUserInfoError, UpdateUsernameError, UserIdExt},
    data::message::Message,
    fairing::user::UserDb,
    guard::userauth::AdminUserData,
    model::user::{PasswordSecret, UserId, UserIdNum, UserInfo, Username},
};
use rocket::http::Status;
use rocket::request::Request;
use rocket::response::{self, Responder, Response};
use rocket::route::Route;
use rocket::serde::json::Json;
use rocket_db_pools::{
    sqlx::{self, Acquire},
    Connection,
};
use serde::Deserialize;

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
struct UpdateUserInfo {
    username: Option<Username>,
    password: Option<String>,
    is_admin: Option<bool>,
}

enum UpdateUserStatus {
    Ok,
    ExistingUsername,
    NoUserFound,
    DatabaseError(sqlx::Error),
    SaltRngFailed(rand::Error),
    InvalidInput,
}

impl<'r> Responder<'r, 'static> for UpdateUserStatus {
    fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
        match self {
            UpdateUserStatus::Ok => Response::build().status(Status::NoContent).ok(),
            UpdateUserStatus::ExistingUsername => Response::build_from(
                Json(Message {
                    message: String::from("已存在用户名相同的用户"),
                })
                .respond_to(req)?,
            )
            .status(Status::PreconditionFailed)
            .ok(),
            UpdateUserStatus::NoUserFound => Response::build().status(Status::NotFound).ok(),
            UpdateUserStatus::DatabaseError(err) => Response::build_from(
                Json(Message {
                    message: err.to_string(),
                })
                .respond_to(req)?,
            )
            .status(Status::InternalServerError)
            .ok(),
            UpdateUserStatus::SaltRngFailed(err) => Response::build_from(
                Json(Message {
                    message: err.to_string(),
                })
                .respond_to(req)?,
            )
            .status(Status::InternalServerError)
            .ok(),
            UpdateUserStatus::InvalidInput => Response::build_from(
                Json(Message {
                    message: String::from("输入为空"),
                })
                .respond_to(req)?,
            )
            .status(Status::PreconditionFailed)
            .ok(),
        }
    }
}

#[patch("/<id>", data = "<user_info>")]
async fn update(
    mut db: Connection<UserDb>,
    _user: AdminUserData,
    id: UserIdNum,
    user_info: Json<UpdateUserInfo>,
) -> UpdateUserStatus {
    let result = async {
        let mut tx = db.begin().await?;

        let id = UserId(id);
        if let Some(username) = user_info.0.username {
            match id.update_username(&mut tx, username).await {
                Ok(_) => (),
                Err(err) => match err {
                    UpdateUsernameError::ExistingUsername => {
                        return Ok(UpdateUserStatus::ExistingUsername)
                    }
                    UpdateUsernameError::DatabaseError(err) => return Err(err),
                    UpdateUsernameError::UserNotFound => return Ok(UpdateUserStatus::NoUserFound),
                    UpdateUsernameError::InvalidUsername => {
                        return Ok(UpdateUserStatus::InvalidInput)
                    }
                },
            }
        }

        if let Some(password) = user_info.0.password {
            match id
                .update_user_password(&mut tx, PasswordSecret(password))
                .await
            {
                Ok(_) => (),
                Err(err) => match err {
                    UpdatePasswordError::EmptyPassword => {
                        return Ok(UpdateUserStatus::InvalidInput)
                    }
                    UpdatePasswordError::DatabaseError(err) => return Err(err),
                    UpdatePasswordError::SaltRngFailed(err) => {
                        return Ok(UpdateUserStatus::SaltRngFailed(err))
                    }
                },
            }
        }

        if let Some(is_admin) = user_info.0.is_admin {
            match id.update_user_info(&mut tx, UserInfo { is_admin }).await {
                Ok(_) => (),
                Err(err) => match err {
                    UpdateUserInfoError::DatabaseError(err) => return Err(err),
                },
            }
        }

        tx.commit().await?;
        Ok(UpdateUserStatus::Ok)
    }
    .await;
    match result {
        Ok(status) => status,
        Err(err) => UpdateUserStatus::DatabaseError(err),
    }
}

pub fn route() -> Vec<Route> {
    routes![update]
}
